package com.jic.market.common.util;

import com.alibaba.fastjson.JSON;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;

@Service
public class RedisService {

    private static final Long LOCK_TIMEOUT = 30L; //加锁超时时间 单位秒


    @Autowired
    protected RedisTemplate<String, String> redisTemplate;

    /**
     * 放置key value 失效时间
     *
     * @param key
     * @param value
     * @param seconds 按秒计算
     */
    public void setValue(final String key, final String value, final Long seconds) {
        redisTemplate.execute(new RedisCallback<Object>() {
            @Override
            public Object doInRedis(RedisConnection connection) throws DataAccessException {
                connection.setEx(redisTemplate.getStringSerializer().serialize(key),
                        seconds, redisTemplate.getStringSerializer().serialize(value));
                return null;
            }
        });
    }

    /**
     * 放置key value 失效时间
     *
     * @param key
     * @param value
     */
    public void setValue(final String key, final String value) {
        redisTemplate.execute(new RedisCallback<Object>() {
            @Override
            public Object doInRedis(RedisConnection connection) throws DataAccessException {
                connection.set(redisTemplate.getStringSerializer().serialize(key),
                        redisTemplate.getStringSerializer().serialize(value));
                return null;
            }
        });
    }

    /**
     * 获取value
     *
     * @param key
     * @return
     */
    public String getValue(final String key) {

        return redisTemplate.execute(new RedisCallback<String>() {
            String value = "";

            @Override
            public String doInRedis(RedisConnection connection) throws DataAccessException {
                byte[] byteKey = redisTemplate.getStringSerializer().serialize(key);
                if (connection.exists(byteKey)) {
                    byte[] bytes = connection.get(byteKey);
                    if (bytes.length > 0) {
                        value = redisTemplate.getStringSerializer().deserialize(bytes);
                        return value;
                    }
                }
                return value;
            }
        });
    }

    /**
     * 返回对象泛型
     *
     * @param key
     * @param cla
     * @return
     */
    public <T> T getObject(final String key, Class<?> cla) {
        String value = this.getValue(key);
        if (!StringUtils.isNotEmpty(value)) {
            return null;
        }
        try {
            @SuppressWarnings("unchecked")
            T t = (T) JSON.parseObject(value, cla);
            return t;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 返回对象list 泛型
     *
     * @param key
     * @param cla
     * @return
     */
    public List<?> getList(final String key, Class<?> cla) {
        String value = this.getValue(key);
        if (!StringUtils.isNotEmpty(value)) {
            return null;
        }
        try {
            List<?> tlist = JSON.parseArray(value, cla);
            return tlist;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 删除
     *
     * @param key
     */
    public void delete(String key) {
        List<String> list = new ArrayList<String>();
        list.add(key);
        delete(list);
    }

    /**
     * 删除多个
     *
     * @param keys
     */
    public void delete(List<String> keys) {
        redisTemplate.delete(keys);
    }

    /**
     * 获取value
     *
     * @param pattern
     * @return
     */
    public List<String> getKeysLike(final String pattern) {
        return redisTemplate.execute(new RedisCallback<List<String>>() {
            List<String> list = new ArrayList<String>();

            @Override
            public List<String> doInRedis(RedisConnection connection) throws DataAccessException {

                Set<String> keys = redisTemplate.keys(pattern);
                Iterator<String> iterator2 = keys.iterator();
                while (iterator2.hasNext()) {
                    String value = iterator2.next();
                    if (StringUtils.isNotEmpty(value)) {
                        list.add(value);
                    }
                }
                return list;
            }
        });
    }

    /**
     * redis加锁
     *
     * @param lockKey
     * @return 到期时间，用于业务解锁
     */
    public synchronized Long lock(String lockKey) {

        while (true) {
            //开始执行加锁
            //到期时间
            Long lock_timeout = currtTimeFromRedis() + LOCK_TIMEOUT * 1000; //锁时间
            Boolean execute = (Boolean) redisTemplate.execute(new RedisCallback<Boolean>() {
                @Override
                public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
                    JdkSerializationRedisSerializer jdkSerializer = new JdkSerializationRedisSerializer();
                    byte[] value = jdkSerializer.serialize(lock_timeout);
                    //只在键 key 不存在的情况下， 将键 key 的值设置为 value 。
                    return connection.setNX(lockKey.getBytes(), value);
                }
            });
            if (execute) {
                //如果加锁成功
                //设置超时时间，释放内存
                RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
                //设置锁在redis里的存活时间，到期自动销毁
                connection.expire(lockKey.getBytes(), LOCK_TIMEOUT);
                connection.close();
                return lock_timeout;
            } else {
                //当前锁存在
                // 获取当前加锁key对应的业务到期时间
                Long currt_lock_timeout_Str = getLongValue(lockKey);

                if (currt_lock_timeout_Str != null && currt_lock_timeout_Str < System.currentTimeMillis()) {
                    //当前时间>到期时间  锁已经失效,但是redis里数据还存在
                    //设置锁的业务到期时间，并返回上次业务到期时间
                    //新的业务，重新设置锁
                    Long old_lock_timeout_Str = getAndSet(lockKey, lock_timeout);

                    // 获取上一个锁到期时间，并设置现在的锁到期时间
                    if (old_lock_timeout_Str != null && old_lock_timeout_Str.equals(currt_lock_timeout_Str)) {
                        // 如过这个时候，多个线程恰好都到了这里，但是只有一个线程的设置值和当前值相同，他才有权利获取锁
                        RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
                        connection.expire(lockKey.getBytes(), LOCK_TIMEOUT);
                        connection.close();
                        // Redis加锁成功
                        return lock_timeout;//返回加锁时间
                    }
                } else {
                    //currt_lock_timeout_Str为空，说明锁已经失效，不做处理
                }
            }

            try {
                TimeUnit.MILLISECONDS.sleep(100);//睡眠100毫秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 业务解锁
     *
     * @param lockKey
     * @param lockvalue
     */
    public synchronized void unlock(String lockKey, long lockvalue) {
        //获取当前加锁key对应的业务到期时间
        Long currt_lock_timeout_Str = getLongValue(lockKey); // redis里的时间
        if (currt_lock_timeout_Str != null && currt_lock_timeout_Str == lockvalue) {
            //如果是加锁者 则删除锁 如果不是则等待自动过期 重新竞争加锁
            redisTemplate.delete(lockKey); //删除键
        }
    }

    /**
     * 获取redis当前时间
     * 多服务器集群，使用下面的方法，代替System.currentTimeMillis()，获取redis时间
     * @return
     */
    public synchronized long currtTimeFromRedis() {

        //获取redis当前时间
        return redisTemplate.execute(new RedisCallback<Long>() {
            @Override
            public Long doInRedis(RedisConnection connection) throws DataAccessException {
                return connection.time();
            }
        });

    }

    /**
     * 获取当前加锁key对应的业务到期时间
     *
     * @param lockKey
     * @return
     */
    private synchronized Long getLongValue(String lockKey) {
        RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
        //返回与键 key 相关联的字符串值
        byte[] valueByte = connection.get(lockKey.getBytes());
        connection.close();
        JdkSerializationRedisSerializer serializer = new JdkSerializationRedisSerializer();
        Long lockValue = (Long) serializer.deserialize(valueByte);
        return lockValue;
    }

    /**
     * 设置锁的业务到期时间，并返回上次业务到期时间
     *
     * @param lockKey  加锁的key
     * @param lockTime 加锁的时间
     * @return
     */
    private synchronized Long getAndSet(String lockKey, Long lockTime) {
        JdkSerializationRedisSerializer jdkSerializer = new JdkSerializationRedisSerializer();
        byte[] value = jdkSerializer.serialize(lockTime);
        RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
        //将键 key 的值设为 value ， 并返回键 key 在被设置之前的旧值。
        byte[] setValue = connection.getSet(lockKey.getBytes(), value);
        connection.close();
        return (Long) jdkSerializer.deserialize(setValue);
    }

}